#include <fstream>
#include <iostream>
#include <sstream>
#include <GL/gl.h>

#include "model/obj.h"

using std::ifstream;
using std::stringstream;

namespace sheeplib {

	namespace model {
			
		/// Strips a line of spaces
		void OBJ::strStrip(string& s) const {
			string t = s;
			string drop = " ";
			string r = t.erase(t.find_last_not_of(drop) + 1);
			s = r.erase(0, r.find_first_not_of(drop));
		}
		
		/// Returns true if the string (s) starts with stringToFind
		bool OBJ::strStartsWith(const string& s, 
									const string& stringToFind) const {
			string t = s;
			strStrip(t);
			return bool(t.find(stringToFind) == 0);
		}
		
		/// Splits a string by delim, and stores it in a vector
		vector<string> OBJ::strSplit(const string& strIn, 
											const string& delim) const {
			vector<string> result;
			unsigned int offset = 0;
			unsigned int index = 0;

			index = strIn.find(delim, offset);

			while (index != string::npos) {
				result.push_back(strIn.substr(offset, index - offset));
				offset += index - offset + delim.length();
				index = strIn.find(delim, offset);
			}

			result.push_back(strIn.substr(offset));

			return result;
		}

		string OBJ::strGetFilePath(const string& filename) {
			string s = filename;
#ifdef WIN32
			s.erase(s.find_last_of("\\") + 1);
#else
			s.erase(s.find_last_of("/") + 1);
#endif
			return s;
		}
		
		/// Read a 3 component vector from the line
		slVec3 OBJ::ReadVectorLine3(const string& line) {
			string data = line.substr(line.find_first_of(" "));
			strStrip(data);
			stringstream streamIn(data);
			
			float x, y, z;
			streamIn >> x >> y >> z;
			
			return slVec3(x, y, z);			
		}

		/// Read a 2 component vector from the line
		slVec2 OBJ::ReadVectorLine2(const string& line) {
			string data = line.substr(line.find_first_of(" "));
			strStrip(data);
			stringstream streamIn(data);
			
			float x, y;
			streamIn >> x >> y;
			
			return slVec2(x, y);			
		}
				
		bool OBJ::ReadVertexLine(const string& line) {
			std::cout << "Reading vertex line" << std::endl;
			slVec3 point = ReadVectorLine3(line);
			m_TempVertices.push_back(point);
			return true;
		}
		
		bool OBJ::ReadTexCoordLine(const string& line) {
			std::cout << "Reading texcoord line" << std::endl;
			slVec2 point = ReadVectorLine2(line);
			m_TempTexCoords.push_back(point);
			return true;
		}
		
		bool OBJ::ReadVertexNormalLine(const string& line) {
			std::cout << "Reading normal line" << std::endl;
			slVec3 point = ReadVectorLine3(line);
			m_TempNormals.push_back(point);
			return true;
		}
		
		
		void OBJ::ProcessFace(	vector<int>& indexes, 
								vector<int>& texIndexes, 
								vector<int>& normIndexes) {
			
			if (!texIndexes.empty()) {
				assert(indexes.size() == texIndexes.size());
			}
			
			if (!normIndexes.empty()) {
				assert(indexes.size() == normIndexes.size());
			}
			
			//std::cout << indexes.size() << std::endl;
			
			for (unsigned int i = 0; i < indexes.size()-1; ++i) {
				int idx1 = indexes[0];
				int idx2 = indexes[i];
				int idx3 = indexes[i + 1];
				
				/* FIXME: Push back the indexes to the correct group in the frame! */
				GetFrame(0).GetGroup(0).GetIndices().push_back(GetFrame(0).GetVertices().size());
				GetFrame(0).GetVertices().push_back(m_TempVertices[idx1]);
				GetFrame(0).GetGroup(0).GetIndices().push_back(GetFrame(0).GetVertices().size());
				GetFrame(0).GetVertices().push_back(m_TempVertices[idx2]);
				GetFrame(0).GetGroup(0).GetIndices().push_back(GetFrame(0).GetVertices().size());
				GetFrame(0).GetVertices().push_back(m_TempVertices[idx3]);
				
				if (!texIndexes.empty()) {
					GetFrame(0).GetTexCoords().push_back(m_TempTexCoords[idx1]);
					GetFrame(0).GetTexCoords().push_back(m_TempTexCoords[idx2]);
					GetFrame(0).GetTexCoords().push_back(m_TempTexCoords[idx3]);
				}
				
				if (!normIndexes.empty()) {
					GetFrame(0).GetNormals().push_back(m_TempNormals[idx1]);
					GetFrame(0).GetNormals().push_back(m_TempNormals[idx2]);
					GetFrame(0).GetNormals().push_back(m_TempNormals[idx3]);
				}
			}
		}
		
		bool OBJ::ReadFaceLine(const string& line) {
			// This is by far the hardest part about reading a OBJ model simply 
			// because the face lines
			// can take a number of forms either:
			// 1. f 1 2 3 4
			// 2. f 1/1 2/2 3/3
			// 3. f 1/1/1 2/2/2 3/3/3
			// 4. f 1//1 2//2 3//3
			
			vector<int> indexes;
			vector<int> texIndexes;
			vector<int> normIndexes;
			
			/* First lets see if there are any slashes, if not we'll assume the 
			first one*/
			
			if (line.find("/") == string::npos) {
				//std::cout << "We have found version 1" << std::endl;
				
				string data = line.substr(line.find_first_of(" "));
				strStrip(data);
				//data should now hold 1 2 3 4 or whatever
				
				//Create a vector of strings "1" "2" "3" "4"
				vector<string> split = strSplit(data, " ");
				for (vector<string>::iterator i = split.begin(); 
												i != split.end(); ++i) {
					int id = -1;
					stringstream streamIn((*i));
					//Convert each string to an actual integer
					streamIn >> id;
					indexes.push_back(id - 1); 
					//We need to negate 1, OBJ file indexes start at 1 not zero
				}
			} else {
				// OK, if we are here then there are either vertex normals, 
				// or texture coordinates
				// First we split it into parts so we have one set of V/VT/VN
				string data = line.substr(line.find_first_of(" "));
				strStrip(data);
				
				vector<string> chunks = strSplit(data, " ");
				
				//Now we attempt to split the first one with a delimiter of 
				//two slashes '//' if the result is not empty then this is
				//version 4 listed above
				vector<string> parts = strSplit(chunks[0], "//");
				
				if (!parts.empty()) {
					for (unsigned int j = 0; j < chunks.size(); ++j) {
						parts = strSplit(chunks[j], "//");
						int v, vn;

						//std::cout << "We have found version 4" << std::endl;
						stringstream(parts[0]) >> v;
						stringstream(parts[1]) >> vn;
						indexes.push_back(v - 1);
						normIndexes.push_back(vn - 1);
					}
					
				} else {
					//Finally split on the slashes and count the results to 
					// determine where to put stuff
					
					for (unsigned int j = 0; j < chunks.size(); ++j) {
						parts = strSplit(chunks[j], "/");
						
						if (parts.size() == 3) {
							int v, vt, vn;

							//std::cout << "We have found version 3" << std::endl;
							stringstream(parts[0]) >> v;
							stringstream(parts[1]) >> vt;
							stringstream(parts[2]) >> vn;
							indexes.push_back(v - 1);
							texIndexes.push_back(vt - 1);
							normIndexes.push_back(vn - 1);
						} else if (parts.size() == 2) {
							int v, vt;

							//std::cout << "We have found version 2" << std::endl;
							stringstream(parts[0]) >> v;
							stringstream(parts[1]) >> vt;
							indexes.push_back(v - 1);
							texIndexes.push_back(vt - 1);
						} else {
							std::cout << "Invalid line!" << std::endl;
						}
						
					}
				}

			}
			
			ProcessFace(indexes, texIndexes, normIndexes);
			return true;
		}
		
		/// Loads the obj model
		bool OBJ::LoadModelFile(const string& filename) {
			std::cout << "Beginning loading OBJ model" << std::endl;
			
			GetFrames().resize(1); //Create 1 frame (a static model after all)
			
			ifstream fileIn(filename.c_str());
			
			if (!fileIn.is_open()) {
				SetErrorString("Could not open the OBJ model: " + filename);
				return false;
			}
			
			string line;
			while(getline(fileIn, line)) {
				std::cout << line << std::endl;
				
				//Ignore lines that start with a hash (comments) or are empty
				if (strStartsWith(line, "#") || line.empty()) 
					continue; 
				
				// If the line starts with a v, it could be a vertex, texcoord 
				// or normal
				if (strStartsWith(line, "v")) {
					if (strStartsWith(line, "vt")) {
						ReadTexCoordLine(line);
						continue;
					} else if (strStartsWith(line, "vn")) {
						ReadVertexNormalLine(line);
						continue;
					} else { //If we get here, it must be a vertex
						ReadVertexLine(line);
						continue;
					}
				}
				
				if (strStartsWith(line, "f")) {
					ReadFaceLine(line); 
					continue;
				}
				
				
				if (strStartsWith(line, "mtllib")) {
					vector<string> split = strSplit(line, " ");
					string file = split[split.size() - 1]; //Get the last element of the split string
					std::cout << "Material file: " << file << std::endl;
					std::cout << "File path: " << strGetFilePath(filename) << std::endl;
					if (!LoadMaterialFile(strGetFilePath(filename), file)) {
						std::cout << "Could not load material file" << std::endl;
					//	return false;
					}
					continue;
				}
			}
			
			fileIn.close();
			
			return true;
		}
		
		bool OBJ::LoadMaterialFile(const string& filepath, const string& filename) {
			
			string fullpath = filepath + filename;
			
			ifstream fileIn(fullpath.c_str());
			
			if (!fileIn.is_open()) {
				SetErrorString("Could not open the material file: " + fullpath);
				return false;
			}
			
			string line;
			string currentMaterial;
			
			while(getline(fileIn, line)) {
				if (strStartsWith(line, "newmtl")) {
					//Create a new material
					
					vector<string> split = strSplit(line, " ");
					currentMaterial = split[split.size() - 1];
					m_Materials.insert(std::make_pair(currentMaterial, ModelMaterial()));
				}
				
				if (strStartsWith(line, "Kd")) {
					//Read the diffuse
					vector<string> split = strSplit(line, " ");
					//float x = boost::lexical_cast<split[1]
					
					//m_Materials[currentMaterial].m_Diffuse
				}
				
				if (strStartsWith(line, "Ka")) {
					//Read the ambient
				}
				
				if (strStartsWith(line, "Ks")) {
					//Read the specular
				}
				
				if (strStartsWith(line, "Ke")) {
					//Read the emissive
				}
				
			}
			
			fileIn.close();
			
			return true;
		}
		
		void OBJ::BasicGLRender() const {
			
		}
		
		void OBJ::slBasicRender() const {
			glEnableClientState(GL_VERTEX_ARRAY);
			glEnableClientState(GL_NORMAL_ARRAY);
				glVertexPointer(3, GL_FLOAT, 0, GetFrame(0).GetVertexPtr());
				glNormalPointer(GL_FLOAT, 0, GetFrame(0).GetNormalPtr());
				glDrawElements(GL_TRIANGLES, GetFrame(0).GetGroup(0).GetIndices().size(),
										GL_UNSIGNED_INT, GetFrame(0).GetGroup(0).GetIndexPtr());

			glDisableClientState(GL_NORMAL_ARRAY);
			glDisableClientState(GL_VERTEX_ARRAY);
		}
		
		void OBJ::slRenderSubset(unsigned int i) const {
			
		}
	}
}
